﻿using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using DimensionsHelper.Common.Contracts.Excel;
using DimensionsHelper.Common.Contracts.Excel.Options;
using DimensionsHelper.Common.Excel;
using DimensionsHelper.Services.Excel;
using MsExcel = Microsoft.Office.Interop.Excel;

namespace DimensionsHelper.ExcelAddIn.Ms.Operator
{

    public class ExcelRangeOperator : IExcelRangeOperator
    {

        public ExcelRangeOperator(IExcelWorksheetOperator worksheet, MsExcel.Range range)
        {
            Worksheet = worksheet;
            RangeInternal = range;
        }

        ~ExcelRangeOperator()
        {
            if (RangeInternal != null)
            {
                Marshal.ReleaseComObject(RangeInternal);
            }
        }


        internal MsExcel.Range RangeInternal { get; }

        public IExcelWorksheetOperator Worksheet { get; }

        public int StartRow => RangeInternal.Row;

        public int EndRow => StartRow + RangeInternal.Rows.Count - 1;

        public int StartColumn => RangeInternal.Column;

        public int EndColumn => StartColumn + RangeInternal.Columns.Count - 1;

        public object Value
        {
            get => RangeInternal.Value2;
            set => RangeInternal.Value2 = value;
        }

        public string Text => RangeInternal.Value2?.ToString() ?? string.Empty;

        public string Address => RangeInternal.Address;

        public string FullAddress => $"'{Worksheet.Name}'!{RangeInternal.Address}";

        public string FullAddressAbsolute => RangeInternal.Address;

        public string NumberFormatLocal
        {
            get => RangeInternal.NumberFormatLocal?.ToString() ?? "general";
            set => RangeInternal.NumberFormatLocal = value;
        }

        public bool WrapText
        {
            get => (bool)RangeInternal.WrapText;
            set => RangeInternal.WrapText = value;
        }

        public float FontSize
        {
            get => (float)RangeInternal.Font.Size;
            set => RangeInternal.Font.Size = value;
        }

        public FontType FontStyle
        {
            get
            {
                var fontType = FontType.None;

                if (RangeInternal.Font.Italic is true)
                {
                    fontType |= FontType.Italic;
                }

                if (RangeInternal.Font.Bold is true)
                {
                    fontType |= FontType.Bold;
                }

                if (!(RangeInternal.Font.Underline is MsExcel.XlUnderlineStyle.xlUnderlineStyleNone))
                {
                    fontType |= FontType.Underline;
                }

                return fontType;
            }

            set
            {
                RangeInternal.Font.Bold = value.HasFlag(FontType.Bold);
                RangeInternal.Font.Italic = value.HasFlag(FontType.Italic);
                RangeInternal.Font.Underline = value.HasFlag(FontType.Underline)
                    ? MsExcel.XlUnderlineStyle.xlUnderlineStyleSingle
                    : MsExcel.XlUnderlineStyle.xlUnderlineStyleNone;
            }
        }

        public ExcelStyleColor FontColor
        {
            get => ((Color)RangeInternal.Font.Color).ToExcelColor();
            set
            {
                if (value != ExcelStyleColor.Empty)
                {
                    RangeInternal.Font.Color = value.ToColor();
                }
            }
        }

        public ExcelStyleColor BackgroundColor
        {
            get => ((Color)RangeInternal.Interior.Color).ToExcelColor();
            set
            {
                if (value != ExcelStyleColor.Empty && value != ExcelStyleColors.White)
                {
                    RangeInternal.Interior.Color = value.ToColor();
                }
            }
        }

        private static MsExcel.XlUnderlineStyle OperatorUnderLineTypeToExcelStyle(UnderLineType underLineType)
        {

            switch (underLineType)
            {
                case UnderLineType.None:
                    return MsExcel.XlUnderlineStyle.xlUnderlineStyleNone;
                case UnderLineType.Single:
                    return MsExcel.XlUnderlineStyle.xlUnderlineStyleSingle;
                case UnderLineType.Double:
                    return MsExcel.XlUnderlineStyle.xlUnderlineStyleDouble;
                case UnderLineType.SingleAccounting:
                    return MsExcel.XlUnderlineStyle.xlUnderlineStyleSingleAccounting;
                case UnderLineType.DoubleAccounting:
                    return MsExcel.XlUnderlineStyle.xlUnderlineStyleDoubleAccounting;
                default:
                    return MsExcel.XlUnderlineStyle.xlUnderlineStyleNone;
            }
        }


        internal static MsExcel.XlVAlign OperatorVerticalAlignmentToExcelAlignment(ExcelVAlign alignment)
        {
            switch (alignment)
            {
                case ExcelVAlign.Top:
                    return MsExcel.XlVAlign.xlVAlignTop;
                case ExcelVAlign.Center:
                    return MsExcel.XlVAlign.xlVAlignCenter;
                case ExcelVAlign.Bottom:
                    return MsExcel.XlVAlign.xlVAlignBottom;
                case ExcelVAlign.Distributed:
                    return MsExcel.XlVAlign.xlVAlignDistributed;
                case ExcelVAlign.Justify:
                    return MsExcel.XlVAlign.xlVAlignJustify;
                default:
                    return MsExcel.XlVAlign.xlVAlignCenter;
            }
        }


        internal static MsExcel.XlHAlign OperatorHorizontalAlignmentToExcelAlignment(ExcelHAlign alignment)
        {
            switch (alignment)
            {
                case ExcelHAlign.General:
                    return MsExcel.XlHAlign.xlHAlignGeneral;
                case ExcelHAlign.Left:
                    return MsExcel.XlHAlign.xlHAlignLeft;
                case ExcelHAlign.Center:
                    return MsExcel.XlHAlign.xlHAlignCenter;
                case ExcelHAlign.Right:
                    return MsExcel.XlHAlign.xlHAlignRight;
                case ExcelHAlign.Fill:
                    return MsExcel.XlHAlign.xlHAlignFill;
                case ExcelHAlign.Distributed:
                    return MsExcel.XlHAlign.xlHAlignDistributed;
                case ExcelHAlign.Justify:
                    return MsExcel.XlHAlign.xlHAlignJustify;
                case ExcelHAlign.CenterContinuous:
                    return MsExcel.XlHAlign.xlHAlignCenterAcrossSelection;
                default:
                    return MsExcel.XlHAlign.xlHAlignGeneral;
            }
        }



        public IExcelRangeOperator AppendRichText(string text, FontStyleOptions fontStyleOptionsParam)
        {
            var value = RangeInternal.Value2?.ToString() ?? string.Empty;
            var startIndex = value.Length;
            RangeInternal.Value2 = value + text;
            var characters = RangeInternal.Characters[startIndex, text.Length];

            if (!string.IsNullOrEmpty(fontStyleOptionsParam.FontFamily))
            {
                characters.Font.Name = fontStyleOptionsParam.FontFamily;
            }

            if (fontStyleOptionsParam.FontSize > 0)
            {
                characters.Font.Size = fontStyleOptionsParam.FontSize;
            }

            characters.Font.Bold = fontStyleOptionsParam.FontType.HasFlag(FontType.Bold);
            characters.Font.Italic = fontStyleOptionsParam.FontType.HasFlag(FontType.Italic);
            characters.Font.Underline = OperatorUnderLineTypeToExcelStyle(fontStyleOptionsParam.UnderLine);

            return this;
        }

        private static MsExcel.XlBorderWeight OperatorBorderStyleToExcelBorderWeight(BorderStyle borderStyle)
        {
            switch (borderStyle)
            {
                case BorderStyle.Hair:
                    return MsExcel.XlBorderWeight.xlHairline;
                case BorderStyle.Thick:
                    return MsExcel.XlBorderWeight.xlThick;
                case BorderStyle.Medium:
                    return MsExcel.XlBorderWeight.xlMedium;
                default:
                    return MsExcel.XlBorderWeight.xlThin;
            }
        }

        private static MsExcel.XlLineStyle OperatorBorderStyleToExcelStyle(BorderStyle borderStyle)
        {
            switch (borderStyle)
            {
                case BorderStyle.Dotted:
                    return MsExcel.XlLineStyle.xlDot;

                case BorderStyle.DashDot:
                case BorderStyle.MediumDashDot:
                    return MsExcel.XlLineStyle.xlDashDot;

                case BorderStyle.DashDotDot:
                case BorderStyle.MediumDashDotDot:
                    return MsExcel.XlLineStyle.xlDashDotDot;

                case BorderStyle.Dashed:
                case BorderStyle.MediumDashed:
                    return MsExcel.XlLineStyle.xlDash;

                case BorderStyle.Double:
                    return MsExcel.XlLineStyle.xlDouble;

                case BorderStyle.None:
                case BorderStyle.Hair:
                case BorderStyle.Thin:
                case BorderStyle.Thick:
                case BorderStyle.Medium:
                default:
                    return MsExcel.XlLineStyle.xlContinuous;
            }
        }

        private static BorderStyle ExcelStyleToOperatorBorderStyle(MsExcel.XlLineStyle borderStyle,
            MsExcel.XlBorderWeight weight)
        {
            switch (borderStyle)
            {
                case MsExcel.XlLineStyle.xlDash:
                    return BorderStyle.Dashed;
                case MsExcel.XlLineStyle.xlDashDot:
                    return BorderStyle.DashDot;
                case MsExcel.XlLineStyle.xlDashDotDot:
                    return BorderStyle.DashDotDot;
                case MsExcel.XlLineStyle.xlDot:
                    return BorderStyle.Dotted;
                case MsExcel.XlLineStyle.xlDouble:
                    return BorderStyle.Double;
                case MsExcel.XlLineStyle.xlSlantDashDot:
                    return BorderStyle.DashDot;
                case MsExcel.XlLineStyle.xlLineStyleNone:
                    return BorderStyle.None;
                default:
                {
                    if (weight == MsExcel.XlBorderWeight.xlHairline)
                    {
                        return BorderStyle.Hair;
                    }

                    if (weight == MsExcel.XlBorderWeight.xlThin)
                    {
                        return BorderStyle.Thick;
                    }

                    return BorderStyle.Thin;
                }
            }
        }

        public ExcelBorderState GetRangeBorderState(ExcelBorderLocation borderLocation)
        {
            MsExcel.XlBordersIndex borderIndex;
            switch (borderLocation)
            {
                case ExcelBorderLocation.Left:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeLeft;
                    break;
                case ExcelBorderLocation.Top:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeTop;
                    break;
                case ExcelBorderLocation.Right:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeRight;
                    break;
                case ExcelBorderLocation.Bottom:
                default:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeBottom;
                    break;
            }

            var border = RangeInternal.Borders[borderIndex];

            return new ExcelBorderState
            {
                BorderStyle = ExcelStyleToOperatorBorderStyle((MsExcel.XlLineStyle)border.LineStyle, (MsExcel.XlBorderWeight)border.Weight),
                IndexedColor = (int)border.ColorIndex,
                Rgb = border.Color?.ToString(),
                ThemeColor = (int)border.ThemeColor
            };
        }

        public bool IsEmpty()
        {
            return RangeInternal.Cells.OfType<MsExcel.Range>()
                .All(cell => string.IsNullOrEmpty(cell.Value2?.ToString()));
        }

        public void Merge() => RangeInternal.Merge();

        public IExcelRangeOperator Offset(int rowOffset, int columnOffset)
        {
            return new ExcelRangeOperator(Worksheet, RangeInternal.Offset[rowOffset, columnOffset]);
        }

        public IExcelRangeOperator SetHyperlink(IExcelRangeOperator target)
        {
            RangeInternal.Hyperlinks.Add(RangeInternal, null, target.FullAddress, TextToDisplay: target.Value?.ToString() ?? "");
            return this;
        }

        public IExcelRangeOperator SetListValidation(string list)
        {
            RangeInternal.Validation.Delete();
            RangeInternal.Validation.Add(MsExcel.XlDVType.xlValidateList, Formula1: list);
            return this;
        }

        public IExcelRangeOperator SetIntegerLessEqualValidation(int upperBound)
        {
            RangeInternal.Validation.Delete();
            RangeInternal.Validation.Add(MsExcel.XlDVType.xlValidateWholeNumber,
                Operator: MsExcel.XlFormatConditionOperator.xlLessEqual,
                Formula1: upperBound);
            return this;
        }

        public IExcelRangeOperator SetIntegerGreaterEqualValidation(int lowerBound)
        {
            RangeInternal.Validation.Delete();
            RangeInternal.Validation.Add(MsExcel.XlDVType.xlValidateWholeNumber, 
                Operator: MsExcel.XlFormatConditionOperator.xlGreaterEqual,
                Formula1: lowerBound);
            return this;
        }

        public IExcelRangeOperator SetIntegerBetweenValidation(int lowerBound, int upperBound)
        {
            RangeInternal.Validation.Delete();
            RangeInternal.Validation.Add(MsExcel.XlDVType.xlValidateWholeNumber, 
                Operator: MsExcel.XlFormatConditionOperator.xlBetween,
                Formula1: lowerBound, 
                Formula2: upperBound);
            return this;
        }

        public IExcelRangeOperator SetRangeBorderColor(BorderPosition borderPosition, BorderStyle borderStyle, ExcelStyleColor borderColor)
        {
            if (borderColor == ExcelStyleColor.Empty || borderStyle == BorderStyle.None)
            {
                return this;
            }

            var drawingColor = borderColor.ToColor();
            var style = OperatorBorderStyleToExcelStyle(borderStyle);
            var weight = OperatorBorderStyleToExcelBorderWeight(borderStyle);

            if ((borderPosition & BorderPosition.Around) != 0)
            {
                RangeInternal.BorderAround2(style, Color: borderColor, Weight: weight);
            }

            if ((borderPosition & BorderPosition.Vertical) != 0)
            {
                var verticalBorder = RangeInternal.Borders[MsExcel.XlBordersIndex.xlInsideVertical];
                verticalBorder.LineStyle = style;
                verticalBorder.Weight = weight;
                verticalBorder.Color = drawingColor;
            }

            if ((borderPosition & BorderPosition.Horizontal) != 0 && RangeInternal.Rows.Count > 1)
            {
                var horizontalBorder = RangeInternal.Borders[MsExcel.XlBordersIndex.xlInsideHorizontal];
                horizontalBorder.LineStyle = style;
                horizontalBorder.Weight = weight;
                horizontalBorder.Color = drawingColor;
            }

            return this;
        }

        public IExcelRangeOperator SetRangeBorderColor(ExcelBorderLocation borderLocation, BorderStyle borderStyle, ExcelStyleColor borderColor)
        {
            MsExcel.XlBordersIndex borderIndex;
            switch (borderLocation)
            {
                case ExcelBorderLocation.Left:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeLeft;
                    break;
                case ExcelBorderLocation.Top:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeTop;
                    break;
                case ExcelBorderLocation.Right:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeRight;
                    break;
                case ExcelBorderLocation.Bottom:
                default:
                    borderIndex = MsExcel.XlBordersIndex.xlEdgeBottom;
                    break;
            }
            var border = RangeInternal.Borders[borderIndex];
            border.LineStyle = OperatorBorderStyleToExcelStyle(borderStyle);
            border.Weight = OperatorBorderStyleToExcelBorderWeight(borderStyle);
            border.Color = borderColor.ToColor();

            return this;
        }

        public IExcelRangeOperator SetRangeBorderStyle(BorderPosition borderPosition, BorderStyle borderStyle)
        {
            var style = OperatorBorderStyleToExcelStyle(borderStyle);
            var weight = OperatorBorderStyleToExcelBorderWeight(borderStyle);

            if ((borderPosition & BorderPosition.Around) != 0)
            {
                RangeInternal.BorderAround2(style, weight);
            }

            if ((borderPosition & BorderPosition.Horizontal) != 0)
            {
                var horizontalBorder = RangeInternal.Borders[MsExcel.XlBordersIndex.xlInsideHorizontal];
                horizontalBorder.LineStyle = style;
                horizontalBorder.Weight = weight;
            }

            if ((borderPosition & BorderPosition.Vertical) != 0)
            {
                var verticalBorder = RangeInternal.Borders[MsExcel.XlBordersIndex.xlInsideVertical];
                verticalBorder.LineStyle = style;
                verticalBorder.Weight = weight;
            }

            return this;
        }

        public IExcelRangeOperator SetRangeFontStyle(FontStyleOptions param)
        {
            if (!string.IsNullOrEmpty(param.FontFamily))
            {
                RangeInternal.Font.Name = param.FontFamily;
            }

            if (param.FontSize > 0)
            {
                RangeInternal.Font.Size = param.FontSize;
            }

            if (param.FontColor != ExcelStyleColor.Empty)
            {
                RangeInternal.Font.Color = param.FontColor.ToColor();
            }

            if ((param.FontType & FontType.Bold) != 0)
            {
                RangeInternal.Font.Bold = true;
            }

            if ((param.FontType & FontType.Italic) != 0)
            {
                RangeInternal.Font.Italic = true;
            }

            if ((param.FontType & FontType.Underline) != 0)
            {
                RangeInternal.Font.Underline = OperatorUnderLineTypeToExcelStyle(param.UnderLine);
            }

            return this;
        }

        public IExcelRangeOperator SetRangeStyleExceptFontStyle(RangeStyleOptions styleOptions)
        {
            RangeInternal.HorizontalAlignment = OperatorHorizontalAlignmentToExcelAlignment(styleOptions.HAlign);
            RangeInternal.VerticalAlignment = OperatorVerticalAlignmentToExcelAlignment(styleOptions.VAlign);
            // Border
            if (styleOptions.BorderPosition != BorderPosition.None && styleOptions.BorderStyle != BorderStyle.None)
            {
                SetRangeBorderStyle(styleOptions.BorderPosition, styleOptions.BorderStyle);
            }
            // Interior
            if (styleOptions.InteriorColor != ExcelStyleColor.Empty && styleOptions.InteriorColor != ExcelStyleColors.White)
            {
                RangeInternal.Interior.Color = styleOptions.InteriorColor.ToColor();
            }
            // Wrap text
            if (styleOptions.WrapText)
            {
                RangeInternal.WrapText = true;
            }
            // NumberFormatLocal
            if (!string.IsNullOrEmpty(styleOptions.NumberFormatLocal))
            {
                RangeInternal.NumberFormatLocal = styleOptions.NumberFormatLocal;
            }

            return this;
        }

        public IExcelRangeOperator SetStyle(RangeStyleOptions styleOptionsParam)
        {
            SetRangeFontStyle(styleOptionsParam.Font);
            SetRangeStyleExceptFontStyle(styleOptionsParam);
            return this;
        }

        
        public string Comment
        {
            get => RangeInternal.Comment.Text();
            set
            {
                RangeInternal.ClearComments();
                RangeInternal.AddComment(value);
            }
        }
    }
}